package com.abcd.collection;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;

/**
 * @program: java-base
 * @description: HashMap的自动扩容机制、设置初始容量
 * @author: Liu
 **/
public class HashMapExpander {

    /**
     * HashMap扩容机制当达到扩容条件之后就会进行扩容。HashMap的扩容条件就是当HashMap的元素个数(size)超过临界值(threshold)时就会自动扩容
     * 在HashMap中 threshold = loadFactor(负载因子) * capacity(容量)
     * HashMap初始化容量设置多少合适
     * 当我们使用HashMap（int initialCapacity）来初始化容量的时候，HashMap并不会直接使用我们传的initialCapacity直接做为初始化容量
     * JDK会默认帮我们计算一个相对合理的值做初始容量，所谓合理的值就是找到一个比传入的initialCapacity大的2的幂
     * 比如 new HashMap(7) JDK通过计算会帮我们创建一个容量为8的Map，当new HashMap(9)会帮我们创建一个容量为16的Map
     * 但是这个并不是完全合理，因为没有考虑到loadFactor(负载因子)，当new HashMap(7) 时 threshold(6) = loadFactor(0.75) * capacity(8)
     * 会进行一次扩容，所以参考JDK8 putAll()方法进行改进
     * return (int) ((float) expectedSize / 0.75F + 1.0F)
     *
     * 当HashMap内部维护的哈希表的容量达到75%（默认情况下的负载因子），会触发rehash(会创建一个容量为现在2倍的数组，然后把现有数组的数据拷贝过去)
     * 这个过程是比较耗时间的，容量设置为expectedSize / 0.75 + 1 可以有效的减少冲突也可以减少误差（应该是高并发情况下HashMap扩容会存在死循环的情况，
     * HashMap主要数据结构为数组加链表，头插法的链表会出现死循环；还有就是put key的时候会根据Hash算法寻址，有可能造成Hash冲突）
     * 这是一个在性能上相对好的选择，会牺牲些内存（认为内存是比较富裕的资源）
     *
     * 也可以换种创建HashMap的方式
     * Maps.newHashMapWithExpectedSize(7)
     */

    public static void main(String[] args) throws Exception {

        // 指定初始容量15来创建一个HashMap
        HashMap m = new HashMap(32);

        // 获取HashMap整个类
        Class<?> mapType = m.getClass();

        // 也可以调用getDeclaredFields()方法获取属性数组
        Field threshold = mapType.getDeclaredField("threshold");

        // 将目标属性设置为可以访问
        threshold.setAccessible(true);

        // 获取指定方法，因为HashMap没有容量这个属性，但是capacity方法会返回容量值
        Method capacity = mapType.getDeclaredMethod("capacity");

        // 设置目标方法为可访问
        capacity.setAccessible(true);

        // 打印刚初始化的HashMap的容量、阈值和元素数量
        System.out.println("容量："+capacity.invoke(m)+" 阈值："+threshold.get(m)+" 元素数量："+m.size());

        for (int i = 0;i<17;i++){

            m.put(i,i);

            // 动态监测HashMap的容量、阈值和元素数量
            System.out.println("容量："+capacity.invoke(m)+" 阈值："+threshold.get(m)+" 元素数量："+m.size());

        }

    }
}